import { Injectable } from '@angular/core';

import { GeneralConfig } from '../config/general.config';
import { WebSocket } from '../models/web-socket.model';

declare const SockJS: any, Stomp: any;

/**
 * A service interacts with backend Web Socket server.
 */
@Injectable()
export class WebSocketService {
    private socket: any;
    private stompClient: any;
    private subscriptions: WebSocket[] = [];
    private activeSubscriptions: WebSocket[] = [];

    constructor() {
        this.subscribeStomp = this.subscribeStomp.bind(this);
    }

    /**
     * Connect to the web socket and start to subscribe.
     * @param inputSubscriptions The list of WebSocket instance.
     */
    connect(inputSubscriptions: WebSocket[]): void {
        // Just filter out the duplicate subscription which already exists in active subscription list.
        this.subscriptions = inputSubscriptions.filter((p: WebSocket) => this.activeSubscriptions.filter((active: WebSocket) => active.destination === p.destination).length === 0);

        // Make sure no duplicate subscription.
        if (this.subscriptions.length > 0) {
            // The url must contain server context path.
            this.socket = new SockJS(GeneralConfig.httpBaseUrl + '/stomp');
            this.stompClient = Stomp.over(this.socket);
            this.stompClient.connect({}, this.subscribeStomp);
        }
    }

    /**
     * Disconnect current web socket.
     */
    disconnect(): void {
        if (this.stompClient !== null) {
            this.stompClient.disconnect();
        }
    }

    /**
     * Unsubscribe by destination.
     * @param destination The string of the subscription destination.
     */
    unsubscribe(destination: string): void {
        // Filter out the matched web socket by the destination.
        let webSocket = this.activeSubscriptions.filter((webSocket: WebSocket) => webSocket.destination === destination);

        // If matches.
        if (webSocket.length === 1) {
            // Unsubscribe this web socket subscription.
            webSocket[0].subscription.unsubscribe();

            // Update the active subscription list.
            this.activeSubscriptions = this.activeSubscriptions.filter((webSocket: WebSocket) => webSocket.destination !== destination);
        }
    }

    /**
     * Unsubscribe current all active subscriptions.
     */
    unsubscribeAll(): void {
        // Unsubscribe all subscriptions.
        this.activeSubscriptions.forEach((webSocket: WebSocket) => webSocket.subscription.unsubscribe());

        // Clear the active subscription list;
        this.activeSubscriptions = [];
    }

    /**
     * Send the data to backend server.
     * @param destination The string of the subscription destination.
     * @param payload The data will sent to the backend server.
     */
    send(destination: string, payload: any): void {
        // The destination must start with "app" and without server context path.
        this.stompClient.send(destination, {}, JSON.stringify(payload));
    }

    /**
     * Start to subscribe on the web socket.
     */
    private subscribeStomp(): void {
        // Subscribe all input destinations.
        this.subscriptions.forEach((webSocket: WebSocket) => {
            // The destination must start with "topic". We use customized destination for specific user messaging.
            webSocket.subscription = this.stompClient.subscribe(webSocket.destination, webSocket.handleMessage);

            // Add it to active subscription list.
            this.activeSubscriptions.push(webSocket);
        });

        // IMPORTANT!!!
        // https://stackoverflow.com/questions/22367223/sending-message-to-specific-user-on-spring-websocket
        // Oh, client side no need to known about current user, server will do that for you.
        // On server side, using following way to send message to an user:
        // simpMessagingTemplate.convertAndSendToUser(username, "/queue/reply", message);
        // Note: Using queue, not topic, Spring always using queue with sendToUser
        // On client side
        // stompClient.subscribe("/user/queue/reply", handler);
        // Explain
        // When any websocket connection is open, Spring will assign it a session id (not HttpSession, assign per connection). 
        // And when your client subscribe to an channel start with /user/, eg: /user/queue/reply, your server instance will subscribe to a queue named queue/reply-user[session id]
        // When use send message to user, eg: username is admin You will write simpMessagingTemplate.convertAndSendToUser("admin", "/queue/reply", message);
        // Spring will determine which session id mapped to user admin. Eg: It found two session wsxedc123 and thnujm456, 
        // Spring will translate it to 2 destination queue/reply-userwsxedc123 and queue/reply-userthnujm456, and it send your message with 2 destinations to your message broker.
        // The message broker receive the messages and provide it back to your server instance that holding session corresponding to each session 
        // (WebSocket sessions can be hold by one or more server). Spring will translate the message to destination (eg: user/queue/reply) and session id (eg: wsxedc123). Then, it send the message to corresponding Websocket session

        // WebSocket Security
        // https://docs.spring.io/spring-security/site/docs/4.0.x/reference/html/websocket.html
        // subscription = this.stompClient.subscribe('/user/topic/chat', function (message: any) {
        //     console.log('Private message');
        //     console.dir(JSON.parse(message.body));
        // });
    }
}